package puzzle.sudoku.data;

import puzzle.common.data.AbstractBoard;
import puzzle.common.data.HatanException;
import puzzle.common.point.Pnt;
import tomoto.util.ArrayUtil;
import tomoto.util.FastBitSet;

public class SudokuBoard extends AbstractBoard<SudokuBoard> {

	public final int base;

	private int[] m_numbers;
	private FastBitSet[] m_forbiddenFlags;
	private NumGroupDictionary m_numGroupDictionary;
	private int m_numOfFixedCells;
	private boolean[] m_assumedFlags;

	public SudokuBoard(int base) {
		super(base * base, base * base);
		this.base = base;

		m_numbers = new int[width * height];

		m_forbiddenFlags = new FastBitSet[width * height];
		for (int i = 0; i < m_forbiddenFlags.length; i++) {
			m_forbiddenFlags[i] = new FastBitSet(width);
		}

		m_numGroupDictionary = new NumGroupDictionary(base);
		m_numOfFixedCells = 0;
		m_assumedFlags = new boolean[width * height];
	}

	public SudokuBoard(SudokuBoard other) {
		super(other);
		this.base = other.base;
		m_numbers = ArrayUtil.dup(other.m_numbers);
		m_forbiddenFlags = ArrayUtil.deepDup(other.m_forbiddenFlags);
		m_numGroupDictionary = other.m_numGroupDictionary; // no need to dup
		m_numOfFixedCells = other.m_numOfFixedCells;
		m_assumedFlags = ArrayUtil.dup(other.m_assumedFlags);
	}

	@Override
	protected void copyFromImpl(SudokuBoard other) {
		assert this.base == other.base;
		ArrayUtil.copy(other.m_numbers, m_numbers);
		ArrayUtil.deepCopy(other.m_forbiddenFlags, m_forbiddenFlags);
		m_numGroupDictionary = other.m_numGroupDictionary; // no need to dup
		m_numOfFixedCells = other.m_numOfFixedCells;
		ArrayUtil.copy(other.m_assumedFlags, m_assumedFlags);
	}

	@Override
	public SudokuBoard dup() {
		return new SudokuBoard(this);
	}

	@Override
	public void snapshotDirtyFlags() {
		// nothing to do
		return;
	}

	public boolean setNumber(int x, int y, int n) throws HatanException {
		assert inRange(x, y);
		assert n != 0;

		int i = x + y * width;

		if (m_numbers[i] != 0) {
			if (n == m_numbers[i]) {
				// already set
				return false;
			} else {
				// cannot change the number
				throw new HatanException();
			}
		}

		if (getForbiddenFlags(x, y).get(n - 1)) {
			throw new HatanException();
		}

		m_numbers[i] = n;
		m_numOfFixedCells++;
		m_forbiddenFlags[i].setAll(true);
		m_forbiddenFlags[i].set(n - 1, false);

		for (NumGroupSet ngs : m_numGroupDictionary.getNumGroupSets()) {
			for (Pnt p : ngs.get(x, y).getPoints()) {
				int n1 = getNumber(p.x, p.y);
				if (n1 != 0) {
					if (n1 != n || (p.x == x && p.y == y)) {
						// ok
					} else {
						throw new HatanException();
					}
				} else {
					forbidNumber(p.x, p.y, n);
				}
			}
		}

		return true;
	}

	public void forbidNumber(int x, int y, int n) {
		m_forbiddenFlags[x + y * width].set(n - 1, true);
	}

	public FastBitSet getForbiddenFlags(int x, int y) {
		return m_forbiddenFlags[x + y * width];
	}

	public int getNumber(int x, int y) {
		assert inRange(x, y);
		return m_numbers[x + y * width];
	}

	public boolean isAssumed(int x, int y) {
		return m_assumedFlags[x + y * width];
	}

	public void setAssumed(int x, int y) {
		m_assumedFlags[x + y * width] = true;
	}

	public int getNumOfFixedCells() {
		return m_numOfFixedCells;
	}

	public FastBitSet getComplementalForbiddenFlags(int x, int y) {
		FastBitSet result = new FastBitSet(width);
		for (NumGroupSet ngs : m_numGroupDictionary.getNumGroupSets()) {
			NumGroup ng = ngs.get(x, y);
			FastBitSet innerResult = new FastBitSet(width);
			innerResult.inv();
			for (Pnt p : ng.getPoints()) {
				FastBitSet f = getForbiddenFlags(p.x, p.y);
				if (p.x == x && p.y == y) {
					innerResult.nand(f);
				} else {
					innerResult.and(f);
				}
			}
			result.or(innerResult);
		}
		return result;
	}
}
